﻿namespace BizVal.Contracts
{
    using System;
    using System.Collections.Generic;

    public class LabelSet : ICollection<TriangularLabel>
    {
        private List<TriangularLabel> labelSet;


        /// <summary>
        /// Gets or sets the <see cref="BizVal.Domain.TriangularLabel"/> at the specified index.
        /// </summary>
        public TriangularLabel this[int index]
        {
            get
            {
                return this.labelSet[index];
            }
            set
            {
                this.labelSet[index] = value;
            }
        }

        /// <summary>
        /// Gets the maximun index of the set.
        /// </summary>
        /// <value>
        /// The maximum index of the set.
        /// </value>
        public int MaxIndex
        {
            get
            {
                return this.Count - 1;

            }
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="LabelSet"/> class.
        /// </summary>
        /// <param name="cardinality">The cardinality.</param>
        public LabelSet(int cardinality)
        {
            this.labelSet = new List<TriangularLabel>(cardinality);
            this.IntializeLabels(cardinality - 1);
        }

        private void IntializeLabels(int maxIndex)
        {
            if (maxIndex < 1)
            {
                // ToDo: Exception.
            }
            decimal step = 1m / maxIndex;
            decimal a, m, b;
            int i = 0;

            // We create the first label.
            a = 0;
            m = 0;
            b = step;
            var firstLabel = this.InitializeLabel(a, m, b, 0);
            this.labelSet.Add(firstLabel);

            for (i = 1; i < maxIndex; i++)
            {
                a = m;
                m = b;
                b = b + step;
                var otherLabel = this.InitializeLabel(a, m, b, i);
                this.labelSet.Add(otherLabel);
            }

            // We create the final label.
            var lastLabel = this.InitializeLabel(m, 1, 1, i);
            this.labelSet.Add(lastLabel);
        }

        private TriangularLabel InitializeLabel(decimal lower, decimal modal, decimal higher, int index)
        {
            TriangularLabel label = new TriangularLabel(lower, modal, higher);
            label.Index = index;
            label.LabelSet = this;
            label.Tuple = new TwoTuple(label, 0);
            return label;
        }


        #region 2-Tuple functions

        /// <summary>
        /// Applies Theta function to the specified term.
        /// </summary>
        /// <param name="term">The term.</param>
        /// <returns>The value.</returns>
        public TwoTuple Theta(TriangularLabel term)
        {
            TwoTuple tupleValue = null;
            if (this.Contains(term))
            {
                tupleValue = new TwoTuple(term, 0);
            }
            else
            {
                // ToDo: Exception.
            }
            return tupleValue;
        }

        /// <summary>
        /// Applies the Delta function the specified value.
        /// </summary>
        /// <param name="betaValue">The beta value.</param>
        /// <returns>The two-tuple equivalent to the input value</returns>
        public TwoTuple Delta(decimal betaValue)
        {
            TwoTuple tupleValue = null;
            if (betaValue >= 0 && betaValue <= this.Count)
            {
                int index = (int)Math.Round(betaValue, MidpointRounding.AwayFromZero);
                TriangularLabel term = this[index];
                decimal alpha = betaValue - index;
                tupleValue = new TwoTuple(term, alpha);
            }
            return tupleValue;
        }

        /// <summary>
        /// Applies the Inverse-Delta function to the specified tuple.
        /// </summary>
        /// <param name="tuple">The tuple.</param>
        /// <returns>The numerical value of the two-tuple.</returns>
        public decimal InverseDelta(TwoTuple tuple)
        {
            decimal value = 0m;
            if (this.Contains(tuple.Label) && tuple.Alpha >= -0.5m && tuple.Alpha < 0.5m)
            {
                value = this.labelSet.IndexOf(tuple.Label) + tuple.Alpha;
            }
            else
            {
                // ToDo: Exception.
            }
            return value;
        }
        #endregion

        #region ICollection implementation


        /// <summary>
        /// Agrega un elemento a <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <param name="item">Objeto que se va a agregar a <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
        /// <exception cref="T:System.NotSupportedException"><see cref="T:System.Collections.Generic.ICollection`1"/> es de sólo lectura.</exception>
        public void Add(TriangularLabel item)
        {
            this.labelSet.Add(item);
        }

        /// <summary>
        /// Quita todos los elementos de <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <exception cref="T:System.NotSupportedException"><see cref="T:System.Collections.Generic.ICollection`1"/> es de sólo lectura. </exception>
        public void Clear()
        {
            this.labelSet.Clear();
        }

        /// <summary>
        /// Determina si <see cref="T:System.Collections.Generic.ICollection`1"/> contiene un valor específico.
        /// </summary>
        /// <param name="item">Objeto que se va a buscar en <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
        /// <returns>
        /// Es true si <paramref name="item"/> se encuentra en <see cref="T:System.Collections.Generic.ICollection`1"/>; en caso contrario, es false.
        /// </returns>
        public bool Contains(TriangularLabel item)
        {
            return this.labelSet.Contains(item);
        }

        /// <summary>
        /// Copies to.
        /// </summary>
        /// <param name="array">The array.</param>
        /// <param name="arrayIndex">Index of the array.</param>
        public void CopyTo(TriangularLabel[] array, int arrayIndex)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Obtiene el número de elementos incluidos en <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <returns>El número de elementos incluidos en <see cref="T:System.Collections.Generic.ICollection`1"/>.</returns>
        public int Count
        {
            get { return this.labelSet.Count; }
        }

        /// <summary>
        /// Obtiene un valor que indica si <see cref="T:System.Collections.Generic.ICollection`1"/> es de sólo lectura.
        /// </summary>
        /// <returns>
        /// Es true si <see cref="T:System.Collections.Generic.ICollection`1"/> es de sólo lectura; en caso contrario, es false.</returns>
        public bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// Quita la primera aparición de un objeto específico de <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>  
        /// <param name="item">Objeto que se va a quitar de <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
        /// <returns>
        /// Es true si <paramref name="item"/> se ha quitado correctamente de <see cref="T:System.Collections.Generic.ICollection`1"/>; en caso contrario, es false. Este método también devuelve false si no se encontró <paramref name="item"/> en la interfaz <see cref="T:System.Collections.Generic.ICollection`1"/> original.
        /// </returns>
        /// <exception cref="T:System.NotSupportedException"><see cref="T:System.Collections.Generic.ICollection`1"/> es de sólo lectura.</exception>
        public bool Remove(TriangularLabel item)
        {
            bool deleted = false;
            if (this.labelSet.Contains(item))
            {
                this.labelSet.Remove(item);
                deleted = true;
            }
            return deleted;
        }

        /// <summary>
        /// Devuelve un enumerador que recorre en iteración la colección.
        /// </summary>
        /// <returns>
        ///   <see cref="T:System.Collections.Generic.IEnumerator`1"/> que se puede utilizar para recorrer en iteración la colección.
        /// </returns>
        public IEnumerator<TriangularLabel> GetEnumerator()
        {
            return labelSet.GetEnumerator();
        }

        /// <summary>
        /// Devuelve un enumerador que recorre en iteración una colección.
        /// </summary>
        /// <returns>
        /// Objeto <see cref="T:System.Collections.IEnumerator"/> que se puede utilizar para recorrer en iteración la colección.
        /// </returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return labelSet.GetEnumerator();
        }

        #endregion
    }
}
